/*---------------------------------------------------------------------------*
 * Copyright (C) 2011-2012 Ryan M. Murphy <ryan.m.murphy.77@gmail.com>
 *
 * File Name   : drv_bms.h
 *
 * Project     : Watts-rock
 *               <http://code.google.com/p/watts-rock-motherboard/>
 *
 * Description : This driver is responsible for controlling the Watts-rock
 *               battery PAC FET, reading the voltage/current,
 *               and monitoring the temperature.
 *
 * References  : 1. AVR121: Enhancing ADC resolution by oversampling.
 *               2. INA170: High-Side, Bidirectional CURRENT SHUNT MONITOR
 * Last Update : Dec 29, 2013
 *---------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------*
 * Include Files
 *---------------------------------------------------------------------------*/
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "arb_device.h"
#include "arb_semaphore.h"
#include "drv_bms.h"
#include "utl_adc.h"
#include "utl_gpio.h"
#include "utl_timer.h"

/*---------------------------------------------------------------------------*
 * Private Defines
 *---------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------*
 * The ADC reference voltage...
 *---------------------------------------------------------------------------*/
#define BMS_ADC_VREF                    (2.507f)
#define BMS_DEFAULT_ADC_SIGNED_NUM_BITS (11) /*2^11 - signed 12 bit*/
#define BMS_CM_ROS                      (10000.0f)
#define BMS_CM_RL                       (4990.0f)
#define BMS_CM_NOM_VOLTS                (BMS_ADC_VREF*BMS_CM_RL/BMS_CM_ROS)

/*---------------------------------------------------------------------------*
 * The number of bits required to change the signed 2^11 ADC value into a
 * signed 2^15 ADC value.
 *---------------------------------------------------------------------------*/
#define BMS_OVERSAMPLING_DESIRED_EXTRA_BITS (4)
#define BMS_OVERSAMPLING_SCALE_FACTOR       (1<<\
BMS_OVERSAMPLING_DESIRED_EXTRA_BITS) /*See [1] eq 3-1*/
#define BMS_OVERSAMPLING_AVERAGE_AMOUNT     (BMS_OVERSAMPLING_SCALE_FACTOR*\
BMS_OVERSAMPLING_SCALE_FACTOR)       /*See [1] eq 3-2*/
#define BMS_OVERSAMPLING_NUM_FRAMES         (2)
#define BMS_DEFAULT_NUM_MEAS                (BMS_OVERSAMPLING_NUM_FRAMES*\
BMS_OVERSAMPLING_AVERAGE_AMOUNT)
#define BMS_CHARGE_PWM_RATE                 (100000) /*Hz*/
#define BMS_CHARGE_INITIAL_PWM_PERC         (.00f)     /*Percent*/
//#define BMS_ADC_DEBUG

#ifdef BMS_ADC_DEBUG
   #define BMS_DEBUG_SIZE 700
   //int16_t as_vltAdcSamples[BMS_DEBUG_SIZE];
   int16_t gs_vltSamplePtr = 0;
   int16_t as_currAdcSamples[BMS_DEBUG_SIZE];
   int16_t gs_currSamplePtr = 0;
   //int16_t as_tempAdcSamples[BMS_DEBUG_SIZE];
   int16_t gs_tempSamplePtr = 0;
#endif

/*---------------------------------------------------------------------------*
 * The TOP fixed-point value of the ADC after oversampling has been
 * performed.
 *---------------------------------------------------------------------------*/
#define BMS_ADC_TOP_SIGNED (float)(\
(int32_t)1 << (BMS_DEFAULT_ADC_SIGNED_NUM_BITS + BMS_OVERSAMPLING_DESIRED_EXTRA_BITS))

/*---------------------------------------------------------------------------*
 * Converts the ADC measurement from a fixed-point value into its floating
 * point representation.
 *---------------------------------------------------------------------------*/
#define BMS_ADC_FXD_TO_FLOAT_SIGNED (BMS_ADC_VREF/BMS_ADC_TOP_SIGNED)

/*---------------------------------------------------------------------------*
 * Private Data Types
 *---------------------------------------------------------------------------*/
typedef struct
{

   /*------------------------------------------------------------------------*
    * Calibration factor that removes the DC bias on the volt meter
    * measurement.
    *------------------------------------------------------------------------*/
   float f_calVltMtrBias;
   /*------------------------------------------------------------------------*
    * The true amount of scaling by the power meter voltage divider. This
    * value is determined during factory calibration by measuring the voltage
    * on both sides on the power meter voltage divider.
    *------------------------------------------------------------------------*/
   float f_calVltMtrRDivide;
   /*------------------------------------------------------------------------*
    * The accumulated bias compensated volt meter ADC measurement.
    *------------------------------------------------------------------------*/
   int32_t i_voltageSum;

}t_voltMeter;

typedef struct
{

   /*------------------------------------------------------------------------*
    * Calibration factor that removes the DC bias on the current meter
    * measurement.
    *------------------------------------------------------------------------*/
   float f_calCurMtrBias;
   /*------------------------------------------------------------------------*
    * The measured value of the r-sense resistor nominally .047 ohms
    *------------------------------------------------------------------------*/
   float f_calRsense;
   /*------------------------------------------------------------------------*
    * The accumulated bias compensated current meter ADC measurement.
    *------------------------------------------------------------------------*/
   int32_t i_currentSum;

}t_currMeter;

typedef struct
{

   /*------------------------------------------------------------------------*
    * Calibration factor that removes the DC bias on the temp meter
    * measurement.
    *------------------------------------------------------------------------*/
   float f_calTempMtrBias;
   /*------------------------------------------------------------------------*
    * The accumulated bias compensated temperature meter ADC measurement.
    *------------------------------------------------------------------------*/
   int32_t i_tempSum;

}t_tempMeter;

typedef struct
{

   /*------------------------------------------------------------------------*
    * Resources that can be shared amongst multiple users (either a global
    * buffer or IO device) need to be protected against race conditions. We
    * use this semaphore for just that purpose.
    *------------------------------------------------------------------------*/
   t_SEMHANDLE t_mutex;
   /*------------------------------------------------------------------------*
    * This semaphore is used for waking up the user-space thread once a
    * measurement has finished.
    *------------------------------------------------------------------------*/
   t_SEMHANDLE t_blockingSem;
   /*------------------------------------------------------------------------*
    * Handle to the ADC used for monitoring the battery voltage.
    *------------------------------------------------------------------------*/
   t_ADCCHANHNDL t_vltMtrHndl;
   /*------------------------------------------------------------------------*
    * Handle to the ADC used for monitoring the battery current.
    *------------------------------------------------------------------------*/
   t_ADCCHANHNDL t_currMtrHndl;
   /*------------------------------------------------------------------------*
    * Handle to the ADC used for monitoring the battery temperature.
    *------------------------------------------------------------------------*/
   t_ADCCHANHNDL t_tempMtrHndl;
   /*------------------------------------------------------------------------*
    * Handle to the ADC used for monitoring the PAC current.
    *------------------------------------------------------------------------*/
   t_ADCCHANHNDL t_pacCurrMtrHndl;
   /*------------------------------------------------------------------------*
    * Contains the factor calibration parameters and battery voltage estimate.
    *------------------------------------------------------------------------*/
   t_voltMeter t_vltMtr;
   /*------------------------------------------------------------------------*
    * Contains the factor calibration parameters for the PAC current estimate.
    *------------------------------------------------------------------------*/
   t_currMeter t_pacCurrMtr;
   /*------------------------------------------------------------------------*
    * Contains the factor calibration parameters for battery current estimate.
    *------------------------------------------------------------------------*/
   t_currMeter t_currMtr;
   /*------------------------------------------------------------------------*
    * Contains the factor calibration parameters and battery temp estimate.
    *------------------------------------------------------------------------*/
   t_tempMeter t_tempMtr;
   /*------------------------------------------------------------------------*
    * Copy of the setup parameters entered at the time of initialization.
    *------------------------------------------------------------------------*/
   t_bmsSetup t_bmsSetup;
   /*------------------------------------------------------------------------*
    * Current average count.
    *------------------------------------------------------------------------*/
   int32_t i_measCount;
   /*------------------------------------------------------------------------*
    * We are going to want to know how many 'handles' or users are attached to
    * this device driver.
    *------------------------------------------------------------------------*/
   uint8_t c_numUsers;

}t_bmsDev;

/*---------------------------------------------------------------------------*
 * Private Function Prototypes
 *---------------------------------------------------------------------------*/
static t_error bmsOpen( t_DEVHANDLE t_handle);

static int16_t bmsRead( t_DEVHANDLE t_handle,
                        int8_t *pc_buff,
                        uint16_t s_size);

static int16_t bmsWrite( t_DEVHANDLE t_handle,
                         int8_t *pc_buff,
                         uint16_t s_size);

static int32_t bmsIoctl( t_DEVHANDLE t_handle,
                         uint16_t s_command,
                         int32_t  i_arguments);

static t_error bmsClose( t_DEVHANDLE t_handle);

static void voltMeasComplete( int16_t s_sample);

static void currMeasComplete( int16_t s_sample);

static void pacCurrMeasComplete( int16_t s_sample);

static void bmsCaptureNewMeas( void);

/*---------------------------------------------------------------------------*
 * Private Global Variables
 *---------------------------------------------------------------------------*/
t_deviceOperations gt_bmsDevOps =
{
    bmsOpen,
    bmsRead,
    bmsWrite,
    bmsIoctl,
    bmsClose

};

/*---------------------------------------------------------------------------*
 * This is the device's shared memory, all actions on this variable must be
 * mutually exclusive.
 *---------------------------------------------------------------------------*/
static t_bmsDev gt_bmsDev;

/*---------------------------------------------------------------------------*
 * Inline Function Definitions
 *---------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------*
 * Private Function Definitions
 *---------------------------------------------------------------------------*/
static void voltMeasComplete( int16_t s_sample)
{
   t_adcError t_adcErr;

   gt_bmsDev.t_vltMtr.i_voltageSum += (int32_t)s_sample;

#ifdef BMS_ADC_DEBUG
   //as_vltAdcSamples[gs_vltSamplePtr] = s_sample;
   gs_vltSamplePtr++;
   if( gs_vltSamplePtr == BMS_DEBUG_SIZE)
      gs_vltSamplePtr = 0;
#endif

   t_adcErr = utl_startAdcConversion( gt_bmsDev.t_tempMtrHndl);

}/*End voltMeasComplete*/

static void tempMeasComplete( int16_t s_sample)
{

   t_adcError t_adcErr;

   gt_bmsDev.t_tempMtr.i_tempSum += (int32_t)s_sample;

#ifdef BMS_ADC_DEBUG
   //as_tempAdcSamples[gs_tempSamplePtr] = s_sample;
   gs_tempSamplePtr++;
   if( gs_tempSamplePtr == BMS_DEBUG_SIZE)
      gs_tempSamplePtr = 0;
#endif

   t_adcErr = utl_startAdcConversion( gt_bmsDev.t_currMtrHndl);

}/*End tempMeasComplete*/

static void currMeasComplete( int16_t s_sample)
{
   t_adcError t_adcErr;

   gt_bmsDev.t_currMtr.i_currentSum += (int32_t)s_sample;

#ifdef BMS_ADC_DEBUG
   //as_currAdcSamples[gs_currSamplePtr] = s_sample;
   //gs_currSamplePtr++;
   //if( gs_currSamplePtr == BMS_DEBUG_SIZE)
   //   gs_currSamplePtr = 0;
#endif

   t_adcErr = utl_startAdcConversion( gt_bmsDev.t_pacCurrMtrHndl);

}/*End currMeasComplete*/

static void pacCurrMeasComplete( int16_t s_sample)
{
   t_adcError t_adcErr;

   gt_bmsDev.t_pacCurrMtr.i_currentSum += (int32_t)s_sample;

   gt_bmsDev.i_measCount++;
   if( gt_bmsDev.i_measCount == BMS_DEFAULT_NUM_MEAS)
   {

      gt_bmsDev.i_measCount = 0;
      arb_signal( gt_bmsDev.t_blockingSem);
   }
   else
   {
      t_adcErr = utl_startAdcConversion( gt_bmsDev.t_vltMtrHndl);
   }

}/*End pacCurrMeasComplete*/

static t_error bmsOpen( t_DEVHANDLE t_handle)
{
   t_error t_err = ARB_PASSED;

   /*------------------------------------------------------------------------*
    * We are going to access global memory or a register, so perform lock
    *------------------------------------------------------------------------*/
   arb_wait( gt_bmsDev.t_mutex,
             0);

   /*------------------------------------------------------------------------*
    * Keep track of the number of user-space applications using the driver.
    *------------------------------------------------------------------------*/
   gt_bmsDev.c_numUsers++;

   /*------------------------------------------------------------------------*
    * Release the lock
    *------------------------------------------------------------------------*/
   arb_signal( gt_bmsDev.t_mutex);

   return t_err;

}/*End bmsOpen*/

static int16_t bmsRead( t_DEVHANDLE t_handle,
                        int8_t *pc_buff,
                        uint16_t s_size)
{
   int16_t s_bufferLevel = 0;

   /*------------------------------------------------------------------------*
    * We are going to access global memory or a register, so perform lock
    *------------------------------------------------------------------------*/
   arb_wait( gt_bmsDev.t_mutex,
             0);

   /*------------------------------------------------------------------------*
    * Read the battery voltage, current, temperature, and AC/DC charger
    * detected status pins.
    *------------------------------------------------------------------------*/

   /*------------------------------------------------------------------------*
    * Release the lock
    *------------------------------------------------------------------------*/
   arb_signal( gt_bmsDev.t_mutex);

   return s_bufferLevel;

}/*End bmsRead*/

static int16_t bmsWrite( t_DEVHANDLE t_handle,
                         int8_t *pc_buff,
                         uint16_t s_size)
{

   /*------------------------------------------------------------------------*
    * We are going to access global memory or a register, so perform lock
    *------------------------------------------------------------------------*/
   arb_wait( gt_bmsDev.t_mutex,
             0);

   /*------------------------------------------------------------------------*
    * Release the lock
    *------------------------------------------------------------------------*/
   arb_signal( gt_bmsDev.t_mutex);

   return s_size;

}/*End bmsWrite*/

static void bmsCaptureNewMeas( void)
{
   t_adcError t_adcErr;

   /*------------------------------------------------------------------------*
    * Reset last measurements..
    *------------------------------------------------------------------------*/
   gt_bmsDev.t_vltMtr.i_voltageSum       = 0;
   gt_bmsDev.t_currMtr.i_currentSum      = 0;
   gt_bmsDev.t_tempMtr.i_tempSum         = 0;
   gt_bmsDev.t_pacCurrMtr.i_currentSum = 0;

   t_adcErr = utl_startAdcConversion( gt_bmsDev.t_vltMtrHndl);

   /*------------------------------------------------------------------------*
    * Wait for the measurements to finish...
    *------------------------------------------------------------------------*/
   arb_wait( gt_bmsDev.t_blockingSem,
             0);

}/*End bmsCaptureNewMeas*/
float f_vSum = 0.0f;
static int32_t bmsIoctl( t_DEVHANDLE t_handle,
                         uint16_t s_command,
                         int32_t i_arguments)
{
   int32_t i_return = (int32_t)BMS_PASSED;

   /*------------------------------------------------------------------------*
    * We are going to access global memory or a register, so perform lock
    *------------------------------------------------------------------------*/
   arb_wait( gt_bmsDev.t_mutex,
             0);

   switch( (t_bmsCmd)s_command)
   {
      case BMS_FAN_CNTRL:

         if( (i_arguments > BMS_DEVICE_ON) || (i_arguments < BMS_DEVICE_OFF))
            i_return = BMS_INVALID_ARG;
         else
         {
            if( i_arguments == BMS_DEVICE_ON)
            {
               utl_gpioOn( gt_bmsDev.t_bmsSetup.c_fanPort,
                           gt_bmsDev.t_bmsSetup.c_fanPin);
            }
            else
            {
               utl_gpioOff( gt_bmsDev.t_bmsSetup.c_fanPort,
                            gt_bmsDev.t_bmsSetup.c_fanPin);
            }

         }

      break;/*End case BMS_FAN_CNTRL:*/

      case BMS_PWR_CNTRL:

         if( (i_arguments > BMS_DEVICE_ON) || (i_arguments < BMS_DEVICE_OFF))
            i_return = BMS_INVALID_ARG;
         else
         {
            if( i_arguments == BMS_DEVICE_ON)
            {
               utl_gpioOn( gt_bmsDev.t_bmsSetup.c_shutdownPort,
                           gt_bmsDev.t_bmsSetup.c_shutdownPin);
            }
            else
            {
               utl_gpioOff( gt_bmsDev.t_bmsSetup.c_shutdownPort,
                            gt_bmsDev.t_bmsSetup.c_shutdownPin);
            }

         }

      break;/*End case BMS_PWR_CNTRL:*/

      case BMS_PACK_CNTRL:

         if( (i_arguments > BMS_DEVICE_ON) || (i_arguments < BMS_DEVICE_OFF))
            i_return = BMS_INVALID_ARG;
         else
         {
            if( i_arguments == BMS_DEVICE_ON)
            {
               utl_gpioOn( gt_bmsDev.t_bmsSetup.c_pacFetPort,
                           gt_bmsDev.t_bmsSetup.c_pacFetPin);
            }
            else
            {
               utl_gpioOff( gt_bmsDev.t_bmsSetup.c_pacFetPort,
                            gt_bmsDev.t_bmsSetup.c_pacFetPin);
            }

         }

      break;/*End case BMS_PACK_CNTRL:*/

      case BMS_STORE_CAL:
      {
         t_bmsCal *pt_bmsCal = (t_bmsCal *)((uint16_t)i_arguments);

         gt_bmsDev.t_vltMtr.f_calVltMtrBias = pt_bmsCal->f_calVltMtrBias;
         gt_bmsDev.t_vltMtr.f_calVltMtrRDivide = pt_bmsCal->f_calVltMtrRDivide;

         gt_bmsDev.t_currMtr.f_calCurMtrBias = pt_bmsCal->f_calCurMtrBias;
         gt_bmsDev.t_currMtr.f_calRsense = pt_bmsCal->f_calRsense;

         gt_bmsDev.t_tempMtr.f_calTempMtrBias = pt_bmsCal->f_calTempMtrBias;

         gt_bmsDev.t_pacCurrMtr.f_calCurMtrBias = pt_bmsCal->f_calPacCurMtrBias;
         gt_bmsDev.t_pacCurrMtr.f_calRsense = pt_bmsCal->f_calPacRsense;

      }
      break;/*End case BMS_STORE_CAL:*/

      case BMS_GET_MEAS:
      {
         t_bmsMeas *pt_bmsMeas;
         float f_volts;
         float f_current;
         float f_temp;
         float f_pacCurrent;

         bmsCaptureNewMeas();

         /*------------------------------------------------------------------*
          * Shift the accumulated values by
          * 2^BMS_OVERSAMPLING_DESIRED_EXTRA_BITS in order to obtain a 2^15
          * bit number.
          *------------------------------------------------------------------*/
         gt_bmsDev.t_vltMtr.i_voltageSum >>=
         BMS_OVERSAMPLING_DESIRED_EXTRA_BITS;
         gt_bmsDev.t_currMtr.i_currentSum >>=
         BMS_OVERSAMPLING_DESIRED_EXTRA_BITS;
         gt_bmsDev.t_tempMtr.i_tempSum >>=
         BMS_OVERSAMPLING_DESIRED_EXTRA_BITS;
         gt_bmsDev.t_pacCurrMtr.i_currentSum >>=
         BMS_OVERSAMPLING_DESIRED_EXTRA_BITS;

#ifdef BMS_ADC_DEBUG
   as_currAdcSamples[gs_currSamplePtr] = gt_bmsDev.t_currMtr.i_currentSum;
   gs_currSamplePtr++;
   if( gs_currSamplePtr == BMS_DEBUG_SIZE)
      gs_currSamplePtr = 0;
#endif

         /*------------------------------------------------------------------*
          * Divide the accumulated values by the number of oversampling fames
          * and convert from a fixed-point ADC value to a floating point
          * voltage, current, and temperature respectively.
          *------------------------------------------------------------------*/
         f_volts = ((float)gt_bmsDev.t_vltMtr.i_voltageSum /
         BMS_OVERSAMPLING_NUM_FRAMES)*BMS_ADC_FXD_TO_FLOAT_SIGNED;
         f_volts = f_volts - gt_bmsDev.t_vltMtr.f_calVltMtrBias;
         f_volts = f_volts*gt_bmsDev.t_vltMtr.f_calVltMtrRDivide;

         f_pacCurrent = ((float)gt_bmsDev.t_pacCurrMtr.i_currentSum /
         BMS_OVERSAMPLING_NUM_FRAMES)*BMS_ADC_FXD_TO_FLOAT_SIGNED;
         f_pacCurrent = (f_pacCurrent - gt_bmsDev.t_pacCurrMtr.f_calCurMtrBias);
         f_pacCurrent /= gt_bmsDev.t_pacCurrMtr.f_calRsense;

         /*------------------------------------------------------------------*
          * Convert from fixed point to floating point and remove bias...
          *------------------------------------------------------------------*/
         f_current = ((float)gt_bmsDev.t_currMtr.i_currentSum /
         BMS_OVERSAMPLING_NUM_FRAMES)*BMS_ADC_FXD_TO_FLOAT_SIGNED;
         f_current = (f_current - gt_bmsDev.t_currMtr.f_calCurMtrBias);
         /*------------------------------------------------------------------*
          * Calculate current through sense resistor using equation 4 of [2].
          *
          *------------------------------------------------------------------*/
         f_current = f_current - BMS_CM_NOM_VOLTS;
         f_current = f_current*1000.0f/(BMS_CM_RL*gt_bmsDev.t_currMtr.f_calRsense);

if( f_current < 0.2127f)
f_current = 0.4537f*f_current-0.0062f;
else if( f_current < 0.2256f)
f_current = 5.4264f*f_current-1.0642f;
else
f_current = f_current*1.0055f-.0922f;

         f_temp = ((float)gt_bmsDev.t_tempMtr.i_tempSum /
         BMS_OVERSAMPLING_NUM_FRAMES)*BMS_ADC_FXD_TO_FLOAT_SIGNED;
         f_temp = (f_temp - gt_bmsDev.t_tempMtr.f_calTempMtrBias);

         /*------------------------------------------------------------------*
          * Calculate the temperature according to section 4.0 of the
          * MCP9700T temperature sensor data sheet, reference
          * http://ww1.microchip.com/downloads/en/DeviceDoc/21942e.pdf for
          * more information.
          *------------------------------------------------------------------*/
         f_temp = (f_temp - .4f) / .0195f;//(f_temp - .5f) / .01f;

         /*------------------------------------------------------------------*
          * Grab pointer to the user-space token variable...
          *------------------------------------------------------------------*/
         pt_bmsMeas = (t_bmsMeas *)((uint16_t)i_arguments);

         /*------------------------------------------------------------------*
          * Populate new measurements...
          *------------------------------------------------------------------*/
         pt_bmsMeas->f_volts      = f_volts;
         pt_bmsMeas->f_current    = f_current;
         pt_bmsMeas->f_pacCurrent = f_pacCurrent;
         pt_bmsMeas->f_temp       = f_temp;

      }
      break;/*End case BMS_GET_MEAS:*/

      default:

         i_return = (int32_t)BMS_INVALID_CMD;

      break;

   }/*End switch( (t_bmsCmd)s_command)*/

   /*------------------------------------------------------------------------*
    * Release the lock
    *------------------------------------------------------------------------*/
   arb_signal( gt_bmsDev.t_mutex);

   return i_return;

}/*End bmsIoctl*/

/*---------------------------------------------------------------------------*
 * Remove this particular file attached to this device
 *---------------------------------------------------------------------------*/
static t_error bmsClose( t_DEVHANDLE t_handle)
{
   t_error t_err = ARB_PASSED;

   /*------------------------------------------------------------------------*
    * We are going to access global memory or a register, so perform lock
    *------------------------------------------------------------------------*/
   arb_wait( gt_bmsDev.t_mutex,
             0);

   /*------------------------------------------------------------------------*
    * Keep track of the number of user-space applications using the driver.
    *------------------------------------------------------------------------*/
   gt_bmsDev.c_numUsers--;

   /*------------------------------------------------------------------------*
    * Release the lock
    *------------------------------------------------------------------------*/
   arb_signal( gt_bmsDev.t_mutex);

   return t_err;

}/*End bmsClose*/

t_error drv_bmsInit( t_bmsSetup t_setup)
{
   t_error t_err = ARB_PASSED;
   t_adcModConf t_adcMod;
   t_adcChanConf t_adcChan;
   t_gpioConf t_gConf;

   /*------------------------------------------------------------------------*
    * Clear the BMS object..
    *------------------------------------------------------------------------*/
   memset( (void *)&gt_bmsDev, 0, sizeof( gt_bmsDev));

   /*------------------------------------------------------------------------*
    * Make sure the kernel is aware that a new device has been loaded.
    *------------------------------------------------------------------------*/
   t_err = arb_registerDevice( "bmsDevice0",
                               arb_createDevId( t_setup.c_majorNum, 0),
                               &gt_bmsDevOps);

   if( t_err < 0)
   {
      goto failed1;
   }

   /*------------------------------------------------------------------------*
    * Request a semaphore from the kernel. Since the signal port is a shared
    * resource we need to have all actions on it be mutually exclusive.
    *------------------------------------------------------------------------*/
   gt_bmsDev.t_mutex = arb_semaphoreCreate( MUTEX);

   if( gt_bmsDev.t_mutex < 0)
   {
      t_err = (t_error)gt_bmsDev.t_mutex;
      goto failed2;

   }/*End if( gt_bmsDev.t_mutex < 0)*/

   /*------------------------------------------------------------------------*
    * Request a semaphore from the kernel. We will use this semaphore for
    * signaling the user-space program when a voltage and current measurement
    * has finished.
    *------------------------------------------------------------------------*/
   gt_bmsDev.t_blockingSem = arb_semaphoreCreate( COUNTING);

   if( gt_bmsDev.t_blockingSem < 0)
   {
      t_err = (t_error)gt_bmsDev.t_blockingSem;
      goto failed3;

   }/*End if( gt_bmsDev.t_blockingSem < 0)*/

   /*------------------------------------------------------------------------*
    * Configure the battery volt meter ADC module.
    *------------------------------------------------------------------------*/
   t_adcMod.t_mode  = SIGNED;
   t_adcMod.t_res   = RES_12BIT;
   t_adcMod.t_ref   = EXTERNAL_PORTA;
   t_adcMod.t_clock = PERIPH_CLOCK_OVER_128;

   if( utl_configureAdcModule( t_setup.t_battVoltAdc, t_adcMod) < 0)
   {
      goto failed4;
   }

   /*------------------------------------------------------------------------*
    * Configure the battery current meter ADC module.
    *------------------------------------------------------------------------*/
   t_adcMod.t_mode  = SIGNED;
   t_adcMod.t_res   = RES_12BIT;
   t_adcMod.t_ref   = EXTERNAL_PORTB;
   t_adcMod.t_clock = PERIPH_CLOCK_OVER_128;

   if( utl_configureAdcModule( t_setup.t_battCurrAdc, t_adcMod) < 0)
   {
     goto failed4;
   }

   /*------------------------------------------------------------------------*
    * Request one of the virtual ADC ports on the given ADC module to perform
    * the battery voltage measuring task.
    *------------------------------------------------------------------------*/
   t_adcChan.c_posPin    = t_setup.c_battVoltPosPin;
   t_adcChan.c_negPin    = t_setup.c_battVoltNegPin;
   t_adcChan.t_inMode    = SINGLE_ENDED_EXT;
   t_adcChan.b_enableInt = true;
   t_adcChan.pf_funPtr   = &voltMeasComplete;

   gt_bmsDev.t_vltMtrHndl = utl_requestAdcChannel( t_setup.t_battVoltAdc,
                                                   t_adcChan);

   if( gt_bmsDev.t_vltMtrHndl < 0)
   {
      goto failed4;
   }

   /*------------------------------------------------------------------------*
    * Request one of the virtual ADC ports on the given ADC module to perform
    * the current measuring task.
    *------------------------------------------------------------------------*/
   t_adcChan.c_posPin    = t_setup.c_battCurrPosPin;
   t_adcChan.c_negPin    = t_setup.c_battCurrNegPin;
   t_adcChan.t_inMode    = SINGLE_ENDED_EXT;
   t_adcChan.b_enableInt = true;
   t_adcChan.pf_funPtr   = &currMeasComplete;

   gt_bmsDev.t_currMtrHndl = utl_requestAdcChannel( t_setup.t_battCurrAdc,
                                                    t_adcChan);

   if( gt_bmsDev.t_currMtrHndl < 0)
   {
      goto failed5;
   }

   /*------------------------------------------------------------------------*
    * Request one of the virtual ADC ports on the given ADC module to perform
    * the BMS module temperature measuring task.
    *------------------------------------------------------------------------*/
   t_adcChan.c_posPin    = t_setup.c_tempPosPin;
   t_adcChan.c_negPin    = t_setup.c_tempNegPin;
   t_adcChan.t_inMode    = SINGLE_ENDED_EXT;
   t_adcChan.b_enableInt = true;
   t_adcChan.pf_funPtr   = &tempMeasComplete;

   gt_bmsDev.t_tempMtrHndl = utl_requestAdcChannel( t_setup.t_tempAdc,
                                                    t_adcChan);

   if( gt_bmsDev.t_tempMtrHndl < 0)
   {
      goto failed6;
   }

   t_gConf.c_inputMask    = 0;
   t_gConf.c_outputMask   = t_setup.c_pacFetPin;
   t_gConf.b_setOutputLow = true;
   t_gConf.t_inConf       = PULLDOWN;
   t_gConf.t_outConf      = TOTEM;

   if( utl_configureGpioPort( t_setup.c_pacFetPort, t_gConf) < 0)
   {
      goto failed7;
   }

   /*------------------------------------------------------------------------*
    * Configure the pin that controls powering on and off the motherboard.
    *------------------------------------------------------------------------*/
   t_gConf.c_inputMask    = 0;
   t_gConf.c_outputMask   = t_setup.c_shutdownPin;
   t_gConf.b_setOutputLow = true;
   t_gConf.t_outConf      = PULLDOWN;

   if( utl_configureGpioPort( t_setup.c_shutdownPort, t_gConf) < 0)
   {
      goto failed7;
   }

   /*------------------------------------------------------------------------*
    * Configure the pin that controls powering on and off the fan.
    *------------------------------------------------------------------------*/
   t_gConf.c_inputMask    = 0;
   t_gConf.c_outputMask   = t_setup.c_fanPin;
   t_gConf.b_setOutputLow = true;
   t_gConf.t_outConf      = PULLDOWN;

   if( utl_configureGpioPort( t_setup.c_fanPort, t_gConf) < 0)
   {
      goto failed7;
   }

   /*------------------------------------------------------------------------*
    * Request one of the virtual ADC ports on the given ADC module to perform
    * the task of measuring the PAC current.
    *------------------------------------------------------------------------*/
   t_adcChan.c_posPin    = t_setup.c_pacCurrPosPin;
   t_adcChan.c_negPin    = t_setup.c_pacCurrNegPin;
   t_adcChan.t_inMode    = SINGLE_ENDED_EXT;
   t_adcChan.b_enableInt = true;
   t_adcChan.pf_funPtr   = &pacCurrMeasComplete;

   gt_bmsDev.t_pacCurrMtrHndl = utl_requestAdcChannel( t_setup.t_pacCurrAdc,
                                                       t_adcChan);

   if( gt_bmsDev.t_pacCurrMtrHndl < 0)
   {
      goto failed8;
   }


   gt_bmsDev.t_bmsSetup = t_setup;
   gt_bmsDev.c_numUsers = 0;

   return ARB_PASSED;

failed8:

   utl_releaseAdcChannel( gt_bmsDev.t_pacCurrMtrHndl);

failed7:

   utl_releaseAdcChannel( gt_bmsDev.t_tempMtrHndl);

failed6:

   utl_releaseAdcChannel( gt_bmsDev.t_currMtrHndl);

failed5:

   utl_releaseAdcChannel( gt_bmsDev.t_vltMtrHndl);

failed4:

   arb_semaphoreDestroy( gt_bmsDev.t_blockingSem);

failed3:

   arb_semaphoreDestroy( gt_bmsDev.t_mutex);

failed2:

   arb_destroyDevice( "bmsDevice0");

failed1:

   return t_err;

}/*End drv_bmsInit*/

/*---------------------------------------------------------------------------*
 * Remove the device from the system
 *---------------------------------------------------------------------------*/
void drv_bmsExit( void)
{

   if( gt_bmsDev.t_mutex != 0) /*If created... destroy*/
   {
      utl_releaseAdcChannel( gt_bmsDev.t_pacCurrMtrHndl);
      utl_releaseAdcChannel( gt_bmsDev.t_tempMtrHndl);
      utl_releaseAdcChannel( gt_bmsDev.t_vltMtrHndl);
      utl_releaseAdcChannel( gt_bmsDev.t_currMtrHndl);
      arb_semaphoreDestroy( gt_bmsDev.t_blockingSem);
      arb_semaphoreDestroy( gt_bmsDev.t_mutex);
      arb_destroyDevice( "bmsDevice0");

      memset( (void *)&gt_bmsDev, 0, sizeof( gt_bmsDev));

   }/*End if( gt_bmsDev.t_mutex != 0)*/

}/*End drv_bmsExit*/
